package org.fffd.l23o6.service.impl;

import java.util.*;
import java.util.stream.Collectors;

import org.fffd.l23o6.dao.RouteDao;
import org.fffd.l23o6.dao.TrainDao;
import org.fffd.l23o6.mapper.TrainMapper;
import org.fffd.l23o6.pojo.entity.RouteEntity;
import org.fffd.l23o6.pojo.entity.TrainEntity;
import org.fffd.l23o6.pojo.enum_.TrainType;
import org.fffd.l23o6.pojo.vo.train.AdminTrainVO;
import org.fffd.l23o6.pojo.vo.train.TrainVO;
import org.fffd.l23o6.pojo.vo.train.TicketInfo;
import org.fffd.l23o6.pojo.vo.train.TrainDetailVO;
import org.fffd.l23o6.service.TrainService;
import org.fffd.l23o6.util.strategy.train.GSeriesSeatStrategy;
import org.fffd.l23o6.util.strategy.train.KSeriesSeatStrategy;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import io.github.lyc8503.spring.starter.incantation.exception.BizException;
import io.github.lyc8503.spring.starter.incantation.exception.CommonErrorType;
import lombok.RequiredArgsConstructor;

import static org.fffd.l23o6.pojo.enum_.TrainType.HIGH_SPEED;

@Service
@RequiredArgsConstructor
public class TrainServiceImpl implements TrainService {
    private final TrainDao trainDao;
    private final RouteDao routeDao;

    @Override
    public TrainDetailVO getTrain(Long trainId) {
        TrainEntity train = trainDao.findById(trainId).get();
        RouteEntity route = routeDao.findById(train.getRouteId()).get();
        return TrainDetailVO.builder().id(trainId).date(train.getDate()).name(train.getName())
                .stationIds(route.getStationIds()).arrivalTimes(train.getArrivalTimes())
                .departureTimes(train.getDepartureTimes()).extraInfos(train.getExtraInfos()).build();
    }

    @Override
    public List<TrainVO> listTrains(Long startStationId, Long endStationId, String date) {
        // TODO
        // First, get all routes contains [startCity, endCity]
        // Then, Get all trains on that day with the wanted routes
        List<RouteEntity> allRoutes = routeDao.findAll();
        List<RouteEntity> temp = new ArrayList<>();
        for (RouteEntity route :
                allRoutes) {
            if (route.getStationIds().contains(startStationId)
                    && route.getStationIds().contains(endStationId)) {
                temp.add(route);
            }
        }
        List<TrainEntity> res = new ArrayList<>();
        for (RouteEntity route :
                temp) {
            List<TrainEntity> trainEntitiesByRouteIdAndDate = trainDao.findTrainEntitiesByRouteIdAndDate(route.getId(), date);
            res.addAll(trainEntitiesByRouteIdAndDate);
        }
        List<TrainVO> resTrainVO = new ArrayList<>();
        for (TrainEntity trainEntity : res) {
            RouteEntity routeEntity = routeDao.findById(trainEntity.getRouteId()).get();
            int startStationIndex = routeEntity.getStationIds().indexOf(startStationId);
            int endStationIndex = routeEntity.getStationIds().indexOf(endStationId);
            List<TicketInfo> tempInfos = new ArrayList<>();
            switch (trainEntity.getTrainType()) {
                case HIGH_SPEED:
                    Map<GSeriesSeatStrategy.GSeriesSeatType, Integer> leftSeatCount =
                            GSeriesSeatStrategy.INSTANCE.getLeftSeatCount(startStationIndex,
                                    endStationIndex, trainEntity.getSeats());
                    for (Map.Entry<GSeriesSeatStrategy.GSeriesSeatType, Integer> entry :
                            leftSeatCount.entrySet()) {
                        tempInfos.add(new TicketInfo(entry.getKey().getText(), entry.getValue(), GSeriesSeatStrategy.INSTANCE.getPrice(entry.getKey())));
                    }
                    break;
                case NORMAL_SPEED:
                    Map<KSeriesSeatStrategy.KSeriesSeatType, Integer> leftSeatCount1 =
                            KSeriesSeatStrategy.INSTANCE.getLeftSeatCount(startStationIndex,
                                    endStationIndex, trainEntity.getSeats());
                    for (Map.Entry<KSeriesSeatStrategy.KSeriesSeatType, Integer> entry :
                            leftSeatCount1.entrySet()) {
                        tempInfos.add(new TicketInfo(entry.getKey().getText(), entry.getValue(), KSeriesSeatStrategy.INSTANCE.getPrice(entry.getKey())));
                    }
                    break;
            }
            TrainVO trainVO = new TrainVO(trainEntity.getId(), trainEntity.getName(), trainEntity.getTrainType().getText(),
                    startStationId, endStationId, trainEntity.getDepartureTimes().get(startStationIndex), trainEntity.getArrivalTimes().get(endStationIndex), tempInfos);
            resTrainVO.add(trainVO);
        }
        return resTrainVO;
    }

    @Override
    public List<AdminTrainVO> listTrainsAdmin() {
        return trainDao.findAll(Sort.by(Sort.Direction.ASC, "name")).stream()
                .map(TrainMapper.INSTANCE::toAdminTrainVO).collect(Collectors.toList());
    }

    @Override
    public void addTrain(String name, Long routeId, TrainType type, String date, List<Date> arrivalTimes,
                         List<Date> departureTimes) {
        TrainEntity entity = TrainEntity.builder().name(name).routeId(routeId).trainType(type)
                .date(date).arrivalTimes(arrivalTimes).departureTimes(departureTimes).build();
        RouteEntity route = routeDao.findById(routeId).get();
        if (route.getStationIds().size() != entity.getArrivalTimes().size()
                || route.getStationIds().size() != entity.getDepartureTimes().size()) {
            throw new BizException(CommonErrorType.ILLEGAL_ARGUMENTS, "列表长度错误");
        }
        entity.setExtraInfos(new ArrayList<String>(Collections.nCopies(route.getStationIds().size(), "预计正点")));
        switch (entity.getTrainType()) {
            case HIGH_SPEED:
                entity.setSeats(GSeriesSeatStrategy.INSTANCE.initSeatMap(route.getStationIds().size()));
                break;
            case NORMAL_SPEED:
                entity.setSeats(KSeriesSeatStrategy.INSTANCE.initSeatMap(route.getStationIds().size()));
                break;
        }
        trainDao.save(entity);
    }

    @Override
    public void changeTrain(Long id, String name, Long routeId, TrainType type, String date, List<Date> arrivalTimes,
                            List<Date> departureTimes) {
        //TODO: edit train info, please refer to `addTrain` above
        //TODO: 这里可能有问题，不知道修改到底是干嘛
        // 个人理解：改变了火车的信息火车的座位表要清空，之前卖的都不算
        TrainEntity entity = TrainEntity.builder().id(id).name(name).routeId(routeId).trainType(type)
                .date(date).arrivalTimes(arrivalTimes).departureTimes(departureTimes).build();
        RouteEntity route = routeDao.findById(routeId).get();
        if (route.getStationIds().size() != entity.getArrivalTimes().size()
                || route.getStationIds().size() != entity.getDepartureTimes().size()) {
            throw new BizException(CommonErrorType.ILLEGAL_ARGUMENTS, "列表长度错误");
        }
        entity.setExtraInfos(new ArrayList<String>(Collections.nCopies(route.getStationIds().size(), "预计正点")));
        switch (entity.getTrainType()) {
            case HIGH_SPEED:
                entity.setSeats(GSeriesSeatStrategy.INSTANCE.initSeatMap(route.getStationIds().size()));
                break;
            case NORMAL_SPEED:
                entity.setSeats(KSeriesSeatStrategy.INSTANCE.initSeatMap(route.getStationIds().size()));
                break;
        }
        trainDao.save(entity);
    }

    @Override
    public void deleteTrain(Long id) {
        trainDao.deleteById(id);
    }
}
